﻿//////////////////////////////////////////////////////////////////////////////
//
//  Copyright © 1998-2024 Glodon Company Limited.  All rights reserved.
//
//  Use of this software is subject to the terms of the Glodon license
//  agreement provided at the time of installation or download, or which
//  otherwise accompanies this software in either electronic or hard copy form.  
//
//////////////////////////////////////////////////////////////////////////////


#pragma once
#include "GnufBase.h"
#include "GnufIAssetDisplay.h"

namespace gnuf {
    
    class GNUF_BASE_EXPORT IShowcaseWatcher : public gcmp::WeakReferenceable
    {
    };    
    class GNUF_BASE_EXPORT Showcase : public gcmp::WeakReferenceable
    {
    public:
        enum class EType : std::uint8_t
        {
            eFreeTiled,
            eGrid2x2Tiled
        };
        template <typename TDerivedClass>
        TDerivedClass * as(const Showcase::EType t)
        { return (t == type() ? static_cast<TDerivedClass *>(this) : nullptr); }
        template <typename TDerivedClass>
        const TDerivedClass * as(const Showcase::EType t) const
        { return (t == type() ? static_cast<const TDerivedClass *>(this) : nullptr); }

        ///\return Return the type.
        Showcase::EType type() const { return m_type; }
    protected:
        Showcase(Showcase::EType);
        ~Showcase();
    private:
        const Showcase::EType m_type;
    };
    
/*    
                      Free                            Grid2x2
            +-----------------------+       +-----------------------+
    _       |   +--------+          |       |         ‖             |
   ( )      |   |        |          |       | Tile-1  ‖ Tile-2      |          _
 ---+---    |   | Tile-1 |------+   |       |         ‖             |         ( )
    |       |   +--------+      |   |       |         ‖             |       ---+---
   / \      |    +--|           |-+ |       |         ‖             |          |
 Watcher    |    |  | Tile-2    | | |       |         ‖             |         / \
            |    |  +-----------+ | |       |         ‖             |      Maintainer
            |  +---------+        | |       |=========o=============|
            |  |         | Tile-4 | |       |         ‖             |
            |  | Tile-3  |--------+ |       |         ‖             |
            |  |         |          |       | Tile-3  ‖ Tile-4      |
            |  +---------+          |       |         ‖             |
            +-----------------------+       +-----------------------+
*/
    class IAssetDisplay;

    class TiledShowcase;
    class GNUF_BASE_EXPORT ITiledShowcaseWatcher : public IShowcaseWatcher
    {
    public:
        virtual void onQuitted(const TiledShowcase *) {}
    };
    class GNUF_BASE_EXPORT IFreeTiledShowcaseWatcher final : public ITiledShowcaseWatcher
    {
    };
    class GNUF_BASE_EXPORT IGridTiledShowcaseWatcher final : public ITiledShowcaseWatcher
    {
    };
    
    class GNUF_BASE_EXPORT TiledShowcase : public Showcase
    {
    protected:
        TiledShowcase(Showcase::EType);
    public:
        virtual ~TiledShowcase();
    public:
        ///\brief Set the watcher for this showcase.
        void setWatcher(ITiledShowcaseWatcher * pWatcher) { m_wpWatcher = pWatcher; }
        ///\return Return the watcher. Return nullptr if not a watcher.
        ITiledShowcaseWatcher * watcher() { return (m_wpWatcher.IsValid() ? m_wpWatcher.Get() : nullptr ); }
        const ITiledShowcaseWatcher * watcher() const { return (m_wpWatcher.IsValid() ? m_wpWatcher.Get() : nullptr ); }
        
        typedef typename std::list< gcmp::WeakPtr< IAssetDisplay > > Tiles;
        typedef typename std::vector< IAssetDisplay * > RawTiles;
        
        ///\return Return true if the tile exists in the showcase.
        bool hasTile(const IAssetDisplay *) const;
        ///\return Return the active tile.
        const IAssetDisplay * activeTile() const;
        ///\return Return the active tile.
        IAssetDisplay * activeTile();
        ///\return Return all of the const tiles.
        const TiledShowcase::Tiles & tiles() const { return m_tiles; }
        ///\return Return all of the tiles.
        TiledShowcase::RawTiles rawTiles();
        ///\brief Set the current tile.
        void setCurrentTile(IAssetDisplay *);
        ///\return Return the current tile. If null, use the active tile, instead.
        const IAssetDisplay * currentTile() const;
        ///\return Return the current tile. If null, use the active tile, instead.
        IAssetDisplay * currentTile();
        ///\return Return true if valid.
        virtual bool isValid() const = 0;
    protected:
        ///\brief Add a tile. Return false, if failed. Otherwise, return true.
        bool appendTile(IAssetDisplay *);
    private:
        gcmp::WeakPtr< ITiledShowcaseWatcher > m_wpWatcher;
        TiledShowcase::Tiles m_tiles;
        gcmp::WeakPtr< IAssetDisplay > m_wpCurrentTile;
    };
     
    class GNUF_BASE_EXPORT FreeTiledShowcase final : public TiledShowcase
    {
    public:
        FreeTiledShowcase();
        virtual ~FreeTiledShowcase();
        bool addTile(IAssetDisplay *);
        virtual bool isValid() const override { return true; }
    };
    
    class GNUF_BASE_EXPORT Grid2x2TiledShowcase final : public TiledShowcase
    {
    public:
        static const std::uint8_t Unknown = 0;
        /*                Layout            |             Number
                                            .
            +-------+-------+-------+-------+-------+-------+-------+-------+
            |   0   |   1   |   2   |   3   |   4   |   5   |   6   |   7   |
            +-------+-------+-------+-------+-------+-------+-------+-------+
              Above   Below   Left    Right .
                                            |
        */
        static const std::uint8_t Layout = 0b11110000;
        
        static const std::uint8_t Number            = 0b00001111;
        static const std::uint8_t NumberAt1stLane   = 0b00001100;
        static const std::uint8_t NumberAt2ndLane   = 0b00000011;
        
        static const std::uint8_t Row    = 0b11000000; // above & below
        static const std::uint8_t Column = 0b00110000; // left & right
        
        static const std::uint8_t OneAndOne = 0b00000101;
        static const std::uint8_t OneAndTwo = 0b00000111;
        static const std::uint8_t TwoAndOne = 0b00001101;
        static const std::uint8_t TwoAndTwo = 0b00001111;
        
        static const std::uint8_t Above1AndBelow1 = Grid2x2TiledShowcase::Row | Grid2x2TiledShowcase::OneAndOne;
        static const std::uint8_t Above1AndBelow2 = Grid2x2TiledShowcase::Row | Grid2x2TiledShowcase::OneAndTwo;
        static const std::uint8_t Above2AndBelow1 = Grid2x2TiledShowcase::Row | Grid2x2TiledShowcase::TwoAndOne;
        static const std::uint8_t Above2AndBelow2 = Grid2x2TiledShowcase::Row | Grid2x2TiledShowcase::TwoAndTwo;
        
        static const std::uint8_t Left1AndRight1 = Grid2x2TiledShowcase::Column | Grid2x2TiledShowcase::OneAndOne;
        static const std::uint8_t Left1AndRight2 = Grid2x2TiledShowcase::Column | Grid2x2TiledShowcase::OneAndTwo;
        static const std::uint8_t Left2AndRight1 = Grid2x2TiledShowcase::Column | Grid2x2TiledShowcase::TwoAndOne;
        static const std::uint8_t Left2AndRight2 = Grid2x2TiledShowcase::Column | Grid2x2TiledShowcase::TwoAndTwo;
        
        enum class EPlacement : std::uint8_t
        {
            eAbove1AndBelow1,
            eAbove1AndBelow2,
            eAbove2AndBelow1,
            eAbove2AndBelow2,
            
            eLeft1AndRight1,
            eLeft1AndRight2,
            eLeft2AndRight1,
            eLeft2AndRight2
        };
        
        using Ratio = std::array<std::uint8_t, 2>;
        static const Grid2x2TiledShowcase::Ratio DefaultRatio; // { 1, 1 }
        
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeAbove1AndBelow1(
            IAssetDisplay * pTileAtAbove1,
            IAssetDisplay * pTileAtBelow1,
            const Grid2x2TiledShowcase::Ratio & heightRatioBetweenRows = Grid2x2TiledShowcase::DefaultRatio);
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeAbove1AndBelow2(
            IAssetDisplay * pTileAtAbove1,
            IAssetDisplay * pTileAtBelow1, IAssetDisplay * pTileAtBelow2,
            const Grid2x2TiledShowcase::Ratio & heightRatioBetweenRows = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenTilesAt2ndRow = Grid2x2TiledShowcase::DefaultRatio);
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeAbove2AndBelow1(
            IAssetDisplay * pTileAtAbove1, IAssetDisplay * pTileAtAbove2,
            IAssetDisplay * pTileAtBelow1,
            const Grid2x2TiledShowcase::Ratio & heightRatioBetweenRows = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenTilesAt1stRow = Grid2x2TiledShowcase::DefaultRatio);
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeAbove2AndBelow2(
            IAssetDisplay * pTileAtAbove1, IAssetDisplay * pTileAtAbove2,
            IAssetDisplay * pTileAtBelow1, IAssetDisplay * pTileAtBelow2,
            const Grid2x2TiledShowcase::Ratio & heightRatioBetweenRows = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenTilesAt1stRow = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenTilesAt2ndRow = Grid2x2TiledShowcase::DefaultRatio);
            
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeLeft1AndRight1(
            IAssetDisplay * pTileAtLeft1,
            IAssetDisplay * pTileAtRight1,
            const Grid2x2TiledShowcase::Ratio & widthRatioBetweenColumns = Grid2x2TiledShowcase::DefaultRatio);
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeLeft1AndRight2(
            IAssetDisplay * pTileAtLeft1,
            IAssetDisplay * pTileAtRight1, IAssetDisplay * pTileAtRight2,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenColumns = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio & heightRatioAt2ndColumn = Grid2x2TiledShowcase::DefaultRatio);
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeLeft2AndRight1(
            IAssetDisplay * pTileAtLeft1,  IAssetDisplay * pTileAtLeft2,
            IAssetDisplay * pTileAtRight1,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenColumns = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio & heightRatioAt1stColumn = Grid2x2TiledShowcase::DefaultRatio);
        static gcmp::OwnerPtr< Grid2x2TiledShowcase > makeLeft2AndRight2(
            IAssetDisplay * pTileAtLeft1,  IAssetDisplay * pTileAtLeft2,
            IAssetDisplay * pTileAtRight1, IAssetDisplay * pTileAtRight2,
            const Grid2x2TiledShowcase::Ratio &  widthRatioBetweenColumns = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio & heightRatioAt1stColumn = Grid2x2TiledShowcase::DefaultRatio,
            const Grid2x2TiledShowcase::Ratio & heightRatioAt2ndColumn = Grid2x2TiledShowcase::DefaultRatio);
        
        virtual ~Grid2x2TiledShowcase();
        
        ///\return Return true if valid.
        virtual bool isValid() const override { return (isEnough() && nullptr != currentTile()); }

        ///\return Return the placement.
        EPlacement placement() const { return m_placement; }
        
/*                                                      .       .       .
                                                        . 1st   . 2nd   .
                                                        . Lane  . Lane  .
                                                        .       .       .
        - - -+-------+-------+- - - - - -               +-------+-------+
             |       |       |                          |       |       |
             | Tile  | Tile  |  1st Lane                | Tile  | Tile  |
             |       |       |                          |       |       |
        - - -+-------+-------+- - - - - -               +-------+-------+
             |       |       |                          |       |       |
             | Tile  | Tile  |  2nd Lane                | Tile  | Tile  |
             |       |       |                          |       |       |
        - - -+-------+-------+- - - - - -               +-------+-------+
                                                        .       .       .
                                                        .       .       .

        Above & Below (Row)                             Left & Right (Column)

        Initialize the tiles in a Z-shape order.        Initialize the tiles in a N-shape order.

        (start) >----/                                  (start)
                    /                                      v   /|
                   /                                       |  / |
                  /---> (end)                              | /  |
                                                           |/   v
                                                              (end)
*/
        inline std::uint8_t style() const;
        const Grid2x2TiledShowcase::Ratio & distanceRatioBetweenLanes()          const { return m_distanceRatioBetweenLanes; }
        const Grid2x2TiledShowcase::Ratio & distanceRatioBetweenTilesAt1stLane() const { return m_distanceRatioBetweenTilesAt1stLane; }
        const Grid2x2TiledShowcase::Ratio & distanceRatioBetweenTilesAt2ndLane() const { return m_distanceRatioBetweenTilesAt2ndLane; }

        void setResizable(bool val) { m_resizable = val; }
        bool  isResizable() const { return m_resizable; }
    private:
        Grid2x2TiledShowcase(EPlacement,
            const Grid2x2TiledShowcase::Ratio &,
            const Grid2x2TiledShowcase::Ratio &,
            const Grid2x2TiledShowcase::Ratio &);
        bool isEnough() const;
        const EPlacement m_placement;
        const Grid2x2TiledShowcase::Ratio m_distanceRatioBetweenLanes;
        const Grid2x2TiledShowcase::Ratio m_distanceRatioBetweenTilesAt1stLane;
        const Grid2x2TiledShowcase::Ratio m_distanceRatioBetweenTilesAt2ndLane;
        bool m_resizable; // not resizable, by default
    };
}